package it.sd.pwframework.persistence.common;



import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityNotFoundException;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class DaoGenericAbstract<K extends Serializable, E> implements
		DaoGeneric<K, E> {

	protected Class<E> entityClass;

	@Autowired
	private SessionFactory sessionFactory;

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	protected Session getCurrentSession() {
		Session session = sessionFactory.getCurrentSession();
		//session.enableFilter("ownedFilter").setParameter("owner", getSecurityOwner());
		return session;
	}

	public void flush() {
		getCurrentSession().flush();
	}

	@SuppressWarnings("unchecked")
	public DaoGenericAbstract() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass()
				.getGenericSuperclass();
		this.entityClass = (Class<E>) genericSuperclass
				.getActualTypeArguments()[1];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#persist(E)
	 */
	public void persist(E entity) {
		
		getCurrentSession().persist(entity);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#delete(E)
	 */
	public void delete(E entity) {
		
		getCurrentSession().delete(entity);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#merge(E)
	 */
	@SuppressWarnings("unchecked")
	public E merge(E entity) {
		
		return (E) getCurrentSession().merge(entity);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#refresh(E)
	 */
	public void refresh(E entity) {
		getCurrentSession().refresh(entity);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#findById(K)
	 */
	@SuppressWarnings("unchecked")
	public E findById(K id) {
		Criteria criteria = getCurrentSession().createCriteria(entityClass)
				.add(Restrictions.idEq(id));
		/*
		if (Grantable.class.isAssignableFrom(entityClass)) {
			criteria = criteria.createCriteria("grantedUsers").add(
					Restrictions.eq("uuId", getLoggedUserUuId()));
		}
		*/
		return (E) criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.uniqueResult();
		// return (E) getCurrentSession().get(entityClass, id);
	}

	public E findByIdCheckExists(K id) throws EntityNotFoundException {
		if (id == null) {
			throw new EntityNotFoundException("cannot find by id with null id");
		}
		E e = findById(id);
		if (e == null) {
			throw new EntityNotFoundException("entity with id: "
					+ id.toString());
		}
		return e;
	}

	public <T> PaginatedResults<T> getPaginatedResultFromCollection(
			Collection<T> collection, Pagination pagination) {
		PaginatedResults<T> result = new PaginatedResults<T>(pagination);
		result.setTotalResults((long) collection.size());
		if (pagination == null) {
			result.addAll(collection);
		} else {
			Integer startIndex = pagination.getPage()
					* pagination.getItemXpage();
			if (startIndex >= collection.size()) {
				return result;
			}
			Integer endIndex = Math.min(
					pagination.getPage() * pagination.getItemXpage()
							+ pagination.getItemXpage(), collection.size());
			result.addAll(new ArrayList<T>(collection).subList(startIndex,
					endIndex));
		}
		return result;
	}

	@SuppressWarnings("rawtypes")
	protected PaginatedResults findByNamedParams(final String query,
			final Map<String, Object> params, final Pagination pagination) {
		ArrayList<String> paramNames = new ArrayList<String>();
		ArrayList<Object> paramValues = new ArrayList<Object>();
		if (params != null) {
			for (String key : params.keySet()) {
				paramNames.add(key);
				paramValues.add(params.get(key));
			}
		}
		return findByNamedParams(query, paramNames.toArray(new String[] {}),
				paramValues.toArray(), pagination);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected PaginatedResults findByNamedParams(final String query,
			final String[] paramsNames, final Object[] paramsValues,
			final Pagination pagination) {
		
		PaginatedResults result = new PaginatedResults(pagination);
		if (pagination == null) {
			Query q = getCurrentSession().createQuery(query);
			if (paramsNames != null && paramsValues != null) {
				for (int i = 0; i < paramsNames.length; i++) {
					applyNamedParameterToQuery(q, paramsNames[i],
							paramsValues[i]);
				}
			}
			result.addAll(q.list());
			result.setTotalResults((long) result.size());
			return result;
		} else {
			Query q = getCurrentSession().createQuery(query);
			if (paramsNames != null && paramsValues != null) {
				for (int i = 0; i < paramsNames.length; i++) {
					applyNamedParameterToQuery(q, paramsNames[i],
							paramsValues[i]);
				}
			}
			q.setMaxResults(pagination.getItemXpage());
			q.setFirstResult(pagination.getPage() * pagination.getItemXpage());
			result.addAll(q.list());
			String countQuery = query;
			Query q2 = getCurrentSession().createQuery(countQuery);
			if (paramsNames != null && paramsValues != null) {
				for (int i = 0; i < paramsNames.length; i++) {
					applyNamedParameterToQuery(q2, paramsNames[i],
							paramsValues[i]);
				}
			}
			Long count = 0L;
			try {
				ScrollableResults scroll = q2.scroll();
				scroll.afterLast();
				scroll.scroll(-1);
				count = (long) (scroll.getRowNumber() + 1);
			} catch (Exception e) {
			}
			result.setTotalResults(count);
			return result;
		}
	}

	@SuppressWarnings("rawtypes")
	protected PaginatedResults findByNamedParam(final String query,
			final String paramName, final Object paramValue,
			final Pagination pagination) {
		return findByNamedParams(query,
				paramName != null ? new String[] { paramName } : null,
				paramValue != null ? new Object[] { paramValue } : null,
				pagination);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#flush(E)
	 */
	public E flush(E entity) {
		getCurrentSession().flush();
		return entity;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#findAll()
	 */
	@SuppressWarnings("unchecked")
	public PaginatedResults<E> findAll() {
		String query = "select e from " + entityClass.getName()+" e ";
		Map<String, Object> params = new HashMap<String, Object>();
		/*
		if (Grantable.class.isAssignableFrom(entityClass)) {
			query = query + " join e.grantedUsers gu where gu.uuId = :loggedUser";
			params.put("loggedUser", getLoggedUserUuId());
		}
		*/
		return (PaginatedResults<E>) findByNamedParams(
				query, params, null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.myorg.birramoretti.hibernate.dao.DaoGenericInterface#removeAll()
	 */
	public Integer removeAll() {
		Query q = getCurrentSession().createQuery(
				"DELETE FROM " + entityClass.getName() + " h");
		return q.executeUpdate();
	}

	public SQLQuery createNativeQuery(final String sqlQuery) {
		return getCurrentSession().createSQLQuery(sqlQuery);
	}

	/**
	 * Apply the given name parameter to the given Query object.
	 * 
	 * @param queryObject
	 *            the Query object
	 * @param paramName
	 *            the name of the parameter
	 * @param value
	 *            the value of the parameter
	 * @throws HibernateException
	 *             if thrown by the Query object
	 */
	protected void applyNamedParameterToQuery(Query queryObject,
			String paramName, Object value) throws HibernateException {

		if (value instanceof Collection) {
			queryObject.setParameterList(paramName, (Collection<?>) value);
		} else if (value instanceof Object[]) {
			queryObject.setParameterList(paramName, (Object[]) value);
		} else {
			queryObject.setParameter(paramName, value);
		}
	}


}
